<?php
/**
 * 文件操作类
 */
declare(strict_types=1);

namespace Libraries;

use Swoole\Coroutine\System;
use finfo;

class File
{
    /* 取目录下文件时用到 */
    const WITH_DIR = 0; // 带上目录
    const ONLY_FILE = 1; // 仅保留文件
    const ONLY_DIR = 2; // 仅保留目录

    /**
     * 取文件扩展名
     * @param string $strFile
     * @return string
     */
    public static function getExt(string $strFile): string
    {
        return pathinfo($strFile, PATHINFO_EXTENSION);
    }

    /**
     * 读取文件中的内容
     * @param string $strFile
     * @return string|null
     */
    public static function read(string $strFile): ?string
    {
        return (file_exists($strFile) && is_file($strFile)) ? file_get_contents($strFile) : null;
    }

    /**
     * 以协程的方式读取文件（调用时此注意，必须处于协程环境）
     * @param string $strFile
     * @return string|null
     */
    public static function coRead(string $strFile): ?string
    {
        $re = System::readFile($strFile);
        return $re ?: null;
    }

    /**
     * 创建文件、写文件
     * @param string $strFile
     * @param string $strContent
     * @param string $mod
     * @return bool
     */
    public static function write(string $strFile, string $strContent = '', string $mod = 'w'): bool
    {
        $re = false;
        self::makeDir(dirname($strFile));
        if (($hdl = fopen($strFile, $mod)) !== false) {
            // flock($hdl, LOCK_EX);
            $re = fwrite($hdl, $strContent);
            // flock($hdl, LOCK_UN);
            fclose($hdl);
        }
        return $re !== false;
    }

    /**
     * 以协程的方式写文件（使用前，请确认处于协程环境，写入内容不得大于4M）
     * @param string $strFile
     * @param string $strContent
     * @param string $mod
     * @return bool
     */
    public static function coWrite(string $strFile, string $strContent = '', string $mod = 'w'): bool
    {
        self::makeDir(dirname($strFile));
        switch ($mod) {
            case 'a':
                $mod = FILE_APPEND;
                break;
            default:
                $mod = 0;
        }
        return System::writeFile($strFile, $strContent, $mod) !== false;
    }

    /**
     * 创建目录
     * @param string $strDir
     * @param int $mod
     * @return bool
     */
    public static function makeDir(string $strDir, int $mod = 0777): bool
    {
        return (file_exists($strDir) && is_dir($strDir)) || mkdir($strDir, $mod, true);
    }

    /**
     * 删除文件
     * @param string $strFile
     * @return bool
     */
    public static function del(string $strFile): bool
    {
        return file_exists($strFile) && is_file($strFile) && unlink($strFile);
    }

    /**
     * 删除目录
     * @param string $strDir
     * @return bool
     */
    public static function delDir(string $strDir): bool
    {
        if (!file_exists($strDir)) {
            return true;
        }
        if (!is_dir($strDir)) {
            return false;
        }
        $hdl = opendir($strDir);
        if ($hdl === false) {
            return false;
        }
        while (($file = readdir($hdl)) !== false) {
            if ($file == '.' || $file == '..') {
                continue;
            }
            $file = $strDir . '/' . $file;
            if (is_dir($file)) {
                self::delDir($file);
            } else {
                self::del($file);
            }
        }
        closedir($hdl);
        return rmdir($strDir);
    }

    /**
     * 将PHP变量写进文件
     * @param string $strFile
     * @param mixed $data
     * @param bool $blnCo 是否以协程方式写入
     * @return bool
     */
    public static function writePhpData(string $strFile, $data, bool $blnCo = false): bool
    {
        $strFun = $blnCo ? 'coWrite' : 'write';
        return self::$strFun($strFile, '<?php' . PHP_EOL . 'return ' . var_export($data, true) . ';' . PHP_EOL);
    }

    /**
     * 取目录下的所有文件
     * @param string $strDir 目标目录
     * @param array $arrExt 指定文件扩展名，如果为空，则取全部
     * @param bool $blnRecursive 是否递归查找子目录
     * @param int $intFlag 是否只取文件、只取目录、包含目录
     * @return array
     */
    public static function getFilesInDir(
        string $strDir,
        array $arrExt = [],
        bool $blnRecursive = true,
        int $intFlag = self::ONLY_FILE
    ): array {
        if (!file_exists($strDir) || !is_dir($strDir)) {
            return [];
        }
        $hdl = opendir($strDir);
        if ($hdl === false) {
            return [];
        }
        $arrRe = [];
        while (($file = readdir($hdl)) !== false) {
            if ($file == '.' || $file == '..') {
                continue;
            }
            $file = $strDir . '/' . $file;
            if (is_dir($file)) {
                in_array($intFlag, [self::ONLY_DIR, self::WITH_DIR], true) && array_push($arrRe, $file);
                if ($blnRecursive) {
                    $arrRe = array_merge($arrRe, self::getFilesInDir($file, $arrExt, $blnRecursive, $intFlag));
                }
                continue;
            }
            if (in_array($intFlag, [self::ONLY_FILE, self::WITH_DIR], true)) {
                if (!$arrExt) {
                    array_push($arrRe, $file);
                    continue;
                }
                in_array(self::getExt($file), $arrExt, true) && array_push($arrRe, $file);
            }
        }
        closedir($hdl);
        return $arrRe;
    }

    /**
     * 取文件总行数
     * @param string $strFile
     * @return int intRe
     */
    public static function countLines(string $strFile): int
    {
        if (!file_exists($strFile) || !is_file($strFile)) {
            return 0;
        }
        $fp = fopen($strFile, 'r');
        if (!$fp) {
            return 0;
        }
        $intRe = 0;
        while (!feof($fp)) {
            if (($strTemp = fread($fp, 2097152)) !== false) {
                $intRe += substr_count($strTemp, PHP_EOL);
            }
        }
        fclose($fp);
        return $intRe;
    }

    /**
     * 友好地显示文件大小
     * @param int $intLen
     * @param string $strGlue
     * @return string
     */
    public static function niceSize(int $intLen, string $strGlue = ''): string
    {
        $arr = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
        for ($i = 0; $intLen >= 1024 && $i < 5; $i++) {
            $intLen /= 1024;
        }
        return (floor($intLen * 100) / 100) . $strGlue . $arr[$i];
    }

    /**
     * 是否合理的目录或文件名称
     * @param string $strName
     * @return bool
     */
    public static function checkName(string $strName): bool
    {
        $intLen = strlen($strName);
        if ($intLen == 0 || $intLen > 200) {
            return false;
        }
        if (str_replace(['/', '\\'], '', $strName) !== $strName) {
            return false;
        }
        if (!preg_match('/^[\w\.\-\x{4e00}-\x{9fa5}]+$/u', $strName)) {
            return false;
        }
        return true;
    }

    /**
     * 检查路径
     * @param string $strPath
     * @param string $strRooPath
     * @return string|null
     */
    public static function checkPath(string $strPath, string $strRooPath = ''): ?string
    {
        $strPathReal = realpath($strPath);
        if ($strPathReal) {
            $strRe = $strPathReal;
        } else {
            $arr = explode('/', trim($strPath, '/'));
            if (array_intersect(['.', '..', '\\'], $arr)) {
                return null;
            }
            $strRe = '';
            foreach ($arr as $s) {
                empty($s) || $strRe .= '/' . $s;
            }
        }
        if ($strRe && $strRooPath && substr($strRe, 0, strlen($strRooPath)) !== $strRooPath) {
            return null;
        }
        return $strRe;
    }

    /**
     * 取文件MIME
     * @param string $strFile
     * @return string|null
     */
    public static function getMimeType(string $strFile): ?string
    {
        $objInfo = new finfo(FILEINFO_MIME_TYPE);
        $strRe = $objInfo->file($strFile);
        return $strRe ?: null;
    }

    /**
     * 上下文旧文件清理
     * @param int $intTotal 每次遍历文件个数
     * @param int $intMaxLife 文件最长有效期，默认2天
     * @return void
     */
    public static function contextGc(int $intTotal = 100, int $intMaxLife = 172800): void
    {
        $strDir = CACHES_PATH . '/context/';
        if (!file_exists($strDir)) {
            return;
        }
        $intCurTime = time();
        $strShellRe = shell_exec('/bin/ls -rt ' . $strDir . ' | /usr/bin/head -n ' . $intTotal); // print_r($arrFiles);
        is_null($strShellRe) && $strShellRe = '';
        $arrFiles = explode(PHP_EOL, $strShellRe);
        foreach ($arrFiles as $strFile) {
            if (empty($strFile)) {
                continue;
            }
            $strFile = $strDir . $strFile;
            if (!is_file($strFile)) {
                continue;
            }
            $intPastTime = $intCurTime - filemtime($strFile);
            if ((filesize($strFile) < 100 && $intPastTime > 1800) || ($intPastTime > $intMaxLife)) {
                self::del($strFile);
            }
        }
        unset($strShellRe, $arrFiles);
    }
}
